#import "Modem.h"
#import <stdio.h>
#import <libc.h>
/* int usleep(unsigned int); */
/* Call 'capture' instead of 'write' to perform filtering.
   For now, we replace cr/lf with lf */

void capture(int fd, char *buf, int size)
{
  static int lastchar=0;

  while (size>0) {
    if (*buf!='\r')
      write(fd,buf,1);
    if (lastchar=='\r' && *buf!='\n') {
      write(fd,&lastchar,1);
      write(fd,buf,1);
    }
    lastchar = *(buf++);
    size--;
  }
}

/* need to catch interrupt if user wished to kill
  child process */
void handler(int sig)
{
  return;
}

@implementation Modem
  
+ newPort:(char *)dev speed:(int)bps
{
  self = [super newReadWriteDevice:dev];
  if (self==nil)
    return self;
  capturefd = (-1);
  sendProt = xmodem1k;
  receiveProt = zmodem;
  [[[[self setRaw] setParity:NOPARITY] unSetCrmod] unSetEcho];
  [self setSpeed:bps];
  return self;
}

+ newA
{
  return [self newPort:'a' speed:2400];
}

+ newB
{
  return [self newPort:'b' speed:2400];
}

- hangup  // by clearing Data Terminal Ready signal to modem
{
  char buf[200];
  /* the following apparently doesn't work */
  printf("\nAttempting to hang up phone via DTR\n");
  ioctl(fd,TIOCCDTR,0);
  sleep(1);
  ioctl(fd,TIOCSDTR,0);
  printf("\nAttempting to hang up phone\n");
  [self readInto:buf]; // flush modems buffer
  [self writeOut:"+++"];
  sleep(4);
  if ([self readInto:buf] && strncmp(buf,"OK",2)) {
    [self writeOut:"ATH\r"];
    printf("success\n");
  }
  return self;
}

- forkExecArgv:(char **)argv
{
  int pid;
  void (*oldhandler)(int);

  pid = fork();
  if (pid!=0) {
    oldhandler = signal(SIGINT, handler);
    wait(0);
    fprintf(stderr,"\r\nFile transfer process ended\r\n");
    signal(SIGINT, oldhandler);
    return self;
  }
  /* the child runs with stdin and stdout redirected to modem */
  dup2(fd,0);
  dup2(fd,1);
  execv(argv[0],argv);
  return self;  // this line is never reached
}

- receiveZmodem
{
  char *argv[]={"/usr/local/bin/rz",NULL};
  return [self forkExecArgv:argv];
}
  
- sendZmodemFile:(char *)filename
{
  char *argv[10];
  argv[0]="/usr/local/bin/sz";
  argv[1]=filename;
  argv[2]=NULL;
  return [self forkExecArgv:argv];
}

- receiveYmodem
{
  char *argv[]={"/usr/local/bin/rb",NULL};
  return [self forkExecArgv:argv];
}
  
- sendYmodemFile:(char *)filename
{
  char *argv[10];
  argv[0]="/usr/local/bin/sb";
  argv[1]=filename;
  argv[2]=NULL;
  return [self forkExecArgv:argv];
}

- receiveXmodemFile:(char *)filename
{
  char *argv[10];
  argv[0]="/usr/local/bin/rx";
  argv[1]=filename;
  argv[2]=NULL;
  return [self forkExecArgv:argv];
}

- sendXmodemFile:(char *)filename
{
  char *argv[10];
  argv[0]="/usr/local/bin/sx";
  argv[1]=filename;
  argv[2]=NULL;
  return [self forkExecArgv:argv];
}

- receiveXmodem1kFile:(char *)filename
{
  char *argv[10];
  argv[0]="/usr/local/bin/rx";
  argv[1]="-k";
  argv[2]=filename;
  argv[3]=NULL;
  return [self forkExecArgv:argv];
}

- sendXmodem1kFile:(char *)filename
{
  char *argv[10];
  argv[0]="/usr/local/bin/sx";
  argv[1]="-k";
  argv[2]=filename;
  argv[3]=NULL;
  return [self forkExecArgv:argv];
}

- receive
{
  char file[200];

  switch (receiveProt) {
  case xmodem:
    printf("\nEnter file in which to receive:");
    scanf("%s",file);
    [self receiveXmodemFile:file];
    break;
  case xmodem1k:
    printf("\nEnter file in which to receive:");
    scanf("%s",file);
    [self receiveXmodem1kFile:file];
    break;
  case ymodem:
    [self receiveYmodem];
    break;
  case zmodem:
    [self receiveZmodem];
    break;
  default:
    fprintf(stderr,"\nIllegal protocol\n\n");
    break;
  }
  return self;
}

- sendFile:(char *)filename
{
  switch (sendProt) {
  case xmodem:
    [self sendXmodemFile:filename];
    break;
  case xmodem1k:
    [self sendXmodem1kFile:filename];
    break;
  case ymodem:
    [self sendYmodemFile:filename];
    break;
  case zmodem:
    [self sendZmodemFile:filename];
    break;
  default:
    fprintf(stderr,"\nIllegal protocol\n\n");
    break;
  }
  return self;
}

- setProtocol:(char)p dir:(enum direction)dir
{
  enum protocol *prot;
  prot = (dir==tohost)? &sendProt: &receiveProt;
  switch (p) {
  case 'x':
  case 'X':
    *prot = xmodem;
    break;
  case '1':
    *prot = xmodem1k;
    break;
  case 'y':
  case 'Y':
    *prot = ymodem;
    break;
  case 'z':
  case 'Z':
    *prot = zmodem;
    break;
  default:
    fprintf(stderr,"\nIllegal protocol, specify x,y,or z\n\n");
    break;
  }
  return self;
}    

- (int) getProtocolDir:(enum direction)dir
{
  enum protocol prot;
  prot = (dir==tohost)? sendProt: receiveProt;
  switch (prot) {
  case xmodem:
    return 'x';
    break;
  case xmodem1k:
    return '1';
    break;
  case ymodem:
    return 'y';
    break;
  case zmodem:
    return 'z';
    break;
  default:
    fprintf(stderr,"\nProtocol Undefined value = %d\n\n",prot);
    break;
  }
  return 0;
}

- captureSessionInFile:(char *)filename
{
  capturefd = open(filename, O_WRONLY | O_APPEND | O_CREAT, 0644);
  return self;
}

- endCapture
{
  close(capturefd);
  capturefd = (-1);
  return self;
}

- (BOOL) isCapturing
{
  return (capturefd >= 0);
}

- (int) readInto:(char *)buf  //returns number of characters read into buf
{
  int chars;
  chars = [super readInto:buf];
  if (capturefd >= 0) capture(capturefd, buf, chars);
  return chars;
}

/* When sending several character strings to a modem,
 * sometimes need to limit the rate.  This simulates
 * typing by hand.
 * The delay is in microseconds, and is only a lower
 * limit.  Because of the overhead of calling usleep(),
 * the pacing is noticeable even when delay = 1 uSec,
 * so I imagine a value of 1 will usually suffice.
 */
- (int) writeOut:(char *)buf paced:(unsigned long)delay
{
  int len = strlen(buf);
  char onechar[2];
  onechar[1]=0;
  while (len-- > 0) {
    onechar[0]=*buf++;
    if ([self writeOut:onechar] < 0)
      return -1;
    usleep(delay);
  }
  return len;
}

@end
